/**
 * Created by henian.xu on 2019/10/22.
 * 公共工具类
 */

import * as Dom from './Dom';
import * as Decode from './Decode';
import * as Valid from './Valid';

export * from './Storage';
export * from './Device';
export * from './GlobalVar';

export { Dom, Decode, Valid };

const urlReg = new RegExp(/[a-zA-z]+:\/\/[^\s]*/);
export const isUrl = (url) => urlReg.test(url);
export const getRawType = (obj) =>
  Object.prototype.toString.call(obj).slice(8, -1);
export const { isArray } = Array;
export const isFunction = (val) => typeof val === 'function';
export const isString = (val) => typeof val === 'string';
export const isNumber = (val) => typeof val === 'number';
export const isBoolean = (val) => typeof val === 'boolean';
export const isObject = (val) => val !== null && typeof val === 'object';
export const hasOwn = (() => {
  const { hasOwnProperty } = Object.prototype;
  return (obj, key) => hasOwnProperty.call(obj, key);
})();
export const isDef = (val) => val !== undefined;

export function assert(condition, msg, module = 'vmf') {
  if (!condition) throw new Error(`[${module}] ${msg}`);
}

/**
 * 是否 VNode
 * @param node
 * @returns {boolean|boolean|*}
 */
export function isVNode(node) {
  return (
    node !== null &&
    typeof node === 'object' &&
    hasOwn(node, 'componentOptions')
  );
}

/**
 * 判断两个对象是否相等
 * @param obj1
 * @param obj2
 * @returns {boolean}
 */
export function isObjEqual(obj1, obj2) {
  if (obj1 === obj2) return true;
  const keys1 = Object.getOwnPropertyNames(obj1);
  const keys2 = Object.getOwnPropertyNames(obj2);
  if (keys1.length !== keys2.length) return false;
  // eslint-disable-next-line no-restricted-syntax
  for (const key of keys1) {
    if (obj1[key] !== obj2[key]) return false;
  }
  return true;
}

/**
 * 判断是否为对象属性
 * @param pattern
 * @param name
 * @returns {boolean}
 */
export function matches(pattern, name) {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(name) > -1;
  }
  if (typeof pattern === 'string') {
    return pattern.split(',').indexOf(name) > -1;
  }
  if (Object.prototype.toString.call(pattern) === '[object RegExp]') {
    return pattern.test(name);
  }
  return false;
}

export const getUniqueId = (() => {
  let autoIncrement = 0;
  return (prefix = '') => {
    autoIncrement += 1;
    const cDate = new Date().getTime();
    const offDate = new Date(2010, 1, 1).getTime();
    const offset = cDate - offDate;
    return prefix + parseFloat(`${offset}`).toString(16) + autoIncrement;
  };
})();

/**
 * 提取对象深层值
 * @param obj
 * @param path
 * @param strict
 * @returns {string}
 */
export function pluckDeep(obj, path, strict = false) {
  if (Array.isArray(path)) {
    path = path.join('.');
  }
  const pathList = `${path}`.split('.');
  const lastIndex = pathList.length - 1;
  return pathList.reduce((prev, curr, index) => {
    if (hasOwn(prev, curr)) {
      return prev[curr];
    }
    if (strict) {
      throw new Error(`[pluckDeep]:${path}超出对像范围`);
    } else if (index !== lastIndex) {
      return {};
    }
    return null;
  }, obj);
}

/**
 * 防抖
 * (与underscore 不同的是 immediate 为 true 时仅仅只是第一次触发会立即调用，
 * 而 underscore immediate 为 true 时会把调用前置，既:触发会立即调用且在 delay 内再触发触发的不调用。
 * 与 underscore 的不同导致不会严格按照 delay 时间间隔调用，因为正好在 delay 调用后触发会立即调用)
 * @param func
 * @param delay
 * @param immediate
 */
export function debounce(func, delay = 200, immediate = false) {
  let timeout = null;
  function debounced(...args) {
    let callback;
    const promise = new Promise((resolve) => {
      callback = resolve;
    });
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const callNow = !timeout;
      // 如果 timeout 为 false 立即调用，侧 setTimeout 就无需重复调用，否侧相反
      timeout = setTimeout(
        (now) => {
          timeout = null;
          if (now) callback(func.apply(this, args));
        },
        delay,
        !callNow,
      );
      if (callNow) callback(func.apply(this, args));
    } else {
      timeout = setTimeout(() => callback(func.apply(this, args)), delay);
    }
    return promise;
  }

  debounced.cancel = () => {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
}

/**
 * 节流
 * underscore的实现
 * @param func
 * @param delay
 * @param options
 */
export function throttle(
  func,
  delay = 200,
  options = { leading: true, trailing: true },
) {
  let timeout;
  let context;
  let _args;
  let previous = 0;
  const cfg = options || {};

  const later = (resolve) => {
    previous = cfg.leading === false ? 0 : new Date().getTime();
    timeout = null;
    resolve(func.apply(context, _args));
    if (!timeout) {
      context = null;
      _args = null;
    }
  };

  function throttled(...args) {
    let callback;
    const promise = new Promise((resolve) => {
      callback = resolve;
    });
    const now = new Date().getTime();
    if (!previous && cfg.leading === false) previous = now;
    const remaining = delay - (now - previous);
    context = this;
    _args = args;
    if (remaining <= 0 || remaining > delay) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      callback(func.apply(context, _args));
      if (!timeout) {
        context = null;
        _args = null;
      }
    } else if (!timeout && cfg.trailing !== false) {
      timeout = setTimeout(later, remaining, callback);
    }
    return promise;
  }

  throttled.cancel = () => {
    clearTimeout(timeout);
    previous = 0;
    timeout = null;
    context = null;
    _args = null;
  };

  return throttled;
}

/**
 * 数值格式化
 * @param value
 * @param length
 * @param strict
 * @returns {*}
 */
export function filterNumber(value, length = 2, strict = false) {
  if (value === null || value === undefined) return '';
  let numberList;
  if (Number.isNaN(value)) {
    numberList = `${value}`.split('-');
  } else {
    numberList = [+value];
  }
  // console.log(numberList);
  return numberList
    .reduce((pre, cur) => {
      let item = '';
      if (!Number.isNaN(cur) && cur !== '') {
        // throw new Error('价格格式化的 value 格式出错');
        item = (+cur).toFixed(length);
        if (!strict) {
          item = +item;
        }
      }
      pre.push(item);
      return pre;
    }, [])
    .join('-');
}

/* 转换 css 的值 */
export function transformCssValue(val, unit = 'px') {
  const int = parseFloat(val);
  if (!int) return '';
  if (!+val) return val;
  return int + unit;
}

export async function arraySomeAsync(arr, callback, thisArg) {
  // eslint-disable-next-line no-restricted-syntax
  for (const [index, item] of Object.entries(arr)) {
    // eslint-disable-next-line no-await-in-loop
    if (await callback(item, +index, isDef(thisArg) ? thisArg : arr))
      return true;
  }
  return false;
}

/**
 * 回到浏览历史首页
 * @param router
 * @param rawLocation
 * @returns {Promise<any>}
 */
export async function reLaunch(router, rawLocation) {
  const historyLength = window.history.length;
  let cb = null;
  let location = rawLocation;
  if (isObject(rawLocation)) {
    location = {
      ...rawLocation,
      replace: true,
    };
  } else if (isString(rawLocation)) {
    location = {
      path: rawLocation,
      replace: true,
    };
  }
  const promise = new Promise((resolve) => {
    cb = resolve;
  });
  const cancelAfterEach = router.afterEach((to) => {
    cancelAfterEach();
    cb(to);
  });

  if (historyLength <= 1) {
    router.replace(location);
    return promise;
  }
  const r = await arraySomeAsync(
    Array(historyLength).fill(1),
    async (item, index) => {
      let cb_ = null;
      let timeId = null;
      const promise_ = new Promise((resolve) => {
        cb_ = resolve;
      });
      const cancelBeforeEach = router.beforeEach((to, from, next) => {
        clearTimeout(timeId);
        cancelBeforeEach();
        next();
        if (location) router.replace(location);
        cb_(to);
      });
      router.go(index - historyLength);
      timeId = setTimeout(() => {
        cancelBeforeEach();
        cb_();
      }, 200);
      return promise_;
    },
  );
  return r ? promise : undefined;
}

// 根据模块类型获取页面url
export function getRouteByModelType({ modelType, id, modelId, url }) {
  if (url) return url;
  if (!modelType || (!id && !modelId)) return '';
  // TODO 实际逻辑未完成
  const ID = modelId || id;
  if (/campaign_work/.test(modelType)) {
    return `/campaign/works/${ID}`;
  }
  if (/campaign|exam|survey/.test(modelType)) {
    return `/${modelType}/${ID}`;
  }
  if (/meeting_course/.test(modelType)) {
    return `/meeting/detail/${ID}`;
  }
  if (/topic/.test(modelType)) {
    return `/forum/detail/${ID}`;
  }
  return `/${modelType}/detail/${ID}`;
}

export const addScript = (() => {
  const head =
    document.head ||
    document.getElementsByTagName('head')[0] ||
    document.documentElement;
  return (src) => {
    return new Promise((resolve, reject) => {
      if (!src) return;
      const script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = src;
      script.onload = () => {
        resolve(script);
      };
      script.onerror = (e) => {
        reject(e);
        head.removeChild(script);
      };
      head.insertBefore(script, head.firstChild);
    });
  };
})();

export const addStyle = (() => {
  const head =
    document.head ||
    document.getElementsByTagName('head')[0] ||
    document.documentElement;
  return (href) => {
    return new Promise((resolve, reject) => {
      if (!href) return;
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.type = 'text/css';
      link.href = href;
      link.onload = () => {
        resolve(link);
      };
      link.onerror = (e) => {
        reject(e);
        head.removeChild(link);
      };
      head.insertBefore(link, head.firstChild);
    });
  };
})();

export function dataURLtoBlob(dataurl) {
  const arr = dataurl.split(',');
  const mime = arr[0].match(/:(.*?);/)[1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n) {
    n -= 1;
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
}

export const createObjectURL = (() => {
  const { createObjectURL: createUrl } = (window || {}).URL || {};
  return (obj) => createUrl(obj);
})();

export const changeOldRouteUrl = (() => {
  const examReg = /^\/exam\/exam\/(\d*)\//;
  const campaignReg = /^\/campaign\/campaign\/(\d*)\//;
  const courseCategoryReg = /^\/course\/category\/(\d*)\//;
  const courseReg = /^\/course\/course\/(\d*)\//;
  const lotteryReg = /^\/lottery\/lottery\/(\d*)\//;
  const mallItemReg = /^\/mall\/item\/(\d*)\//;
  return (path) => {
    if (examReg.test(path)) {
      return path.replace(examReg, (str, g1) => {
        return `/exam/${g1}`;
      });
    }
    if (campaignReg.test(path)) {
      return path.replace(campaignReg, (str, g1) => {
        return `/campaign/${g1}`;
      });
    }
    if (courseCategoryReg.test(path)) {
      return `${path}`;
    }
    if (courseReg.test(path)) {
      return path.replace(courseReg, (str, g1) => {
        return `/course/detail/${g1}`;
      });
    }
    if (lotteryReg.test(path)) {
      return path.replace(lotteryReg, (str, g1) => {
        return `/lottery/${g1}`;
      });
    }
    if (mallItemReg.test(path)) {
      return path.replace(mallItemReg, (str, g1) => {
        return `/mall/goodsDetail/${g1}`;
      });
    }
    return path;
  };
})();

const cacheStringFunction = (fn) => {
  const cache = Object.create(null);
  return (str, ...args) => {
    const hit = cache[str];
    // eslint-disable-next-line no-return-assign
    return hit || (cache[str] = fn(str, ...args));
  };
};

const camelizeRE = /-(\w)/g;
/**
 * @private
 */
export const camelize = cacheStringFunction((str) => {
  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});

const hyphenateRE = /\B([A-Z])/g;
/**
 * @private
 */
export const hyphenate = cacheStringFunction((str, underscore = false) => {
  return str.replace(hyphenateRE, `${underscore ? '_' : '-'}$1`).toLowerCase();
});
